package han.chensing.latos.maker.latos.config;

import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.lib.jse.JsePlatform;

import java.io.Reader;
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import han.chensing.latos.common.file.CirnoSerialize;
import han.chensing.latos.common.level.content.objects.CommonLine;
import han.chensing.latos.maker.latos.config.parameter.ConfigParameter;

public class LatosConfig implements Serializable {

    public static Globals luaGlobals=JsePlatform.standardGlobals();
    protected static CirnoSerialize cirnoSerialize=new CirnoSerialize();
    @Serial
    private static final long serialVersionUID = 2306153191818438739L;

    protected String configType;
    protected String name;
    protected String author;
    protected String description;
    protected LuaFunction noteTaskFunction;
    protected LuaFunction lineTaskFunction;
    protected List<ConfigParameter> parameters;

    private LatosConfig(){
        this.parameters=new ArrayList<>();
    }

    public static LatosConfig fromLuaFile(Reader reader) {
        LatosConfig latosConfig=new LatosConfig();
        LuaValue config = luaGlobals.load(reader, "config.lua");
        config.call();
        latosConfig.applyArgumentsTable((LuaTable) invokeLuaFunction("arguments"));
        latosConfig.applyMeta();
        latosConfig.applyFunction();
        return latosConfig;
    }

    public void invokeApply()
            throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        /*LuaTable timeLineTable = cirnoSerialize.serializeToLuaTable(V.project.getLevelData().getTimeLine()); TODO
        Varargs timeLineReturn = lineTaskFunction.invoke(new LuaValue[]{timeLineTable});
        LuaTable outTimeLineTable = timeLineReturn.arg1().checktable();
        TimeLine newTimeLine = (TimeLine) cirnoSerialize.luaTableToObject(outTimeLineTable);
        //if (newTimeLine!=null)V.project.getLevelData().setTimeLine(newTimeLine);
        Varargs notesReturn = noteTaskFunction.invoke(new LuaValue[]{cirnoSerialize.serializeToLuaTable(
                new CommonLineContainer(V.project.getLevelData().getLineObjects()))});
        LuaTable outNotesTable = notesReturn.arg1().checktable();
        CommonLineContainer newCommonLineContainer = (CommonLineContainer) cirnoSerialize.luaTableToObject(outNotesTable);
        if (newCommonLineContainer!=null&&newCommonLineContainer.commonLines!=null)
            V.project.getLevelData().setLines(newCommonLineContainer.commonLines);*/
    }

    private void applyFunction(){
        try {
            this.noteTaskFunction = luaGlobals.get("applyNote").checkfunction();
        }catch (Exception ignore){}
        try{
            this.lineTaskFunction= luaGlobals.get("applyLine").checkfunction();
        }catch (Exception ignore){}
    }

    private void applyMeta(){
        LuaTable luaTable = (LuaTable) invokeLuaFunction("meta");
        configType=luaTable.get(1).tojstring();
        name=luaTable.get(4).tojstring();
        author=luaTable.get(3).tojstring();
        description=luaTable.get(4).tojstring();
    }

    private void applyArgumentsTable(LuaTable argumentTable){
        LuaValue[] keys = argumentTable.keys();
        for (LuaValue key : keys) {
            LuaTable luaTable = (LuaTable) argumentTable.get(key);
            ConfigParameter configParameter = ConfigFactory.getInstance().constructConfig(luaTable);
            parameters.add(configParameter);
        }
    }

    private static LuaValue invokeLuaFunction(String func, Object... parameters) {
        if (parameters != null && parameters.length > 0) {
            LuaValue[] values = new LuaValue[parameters.length];
            for (int i = 0; i < parameters.length; i++)
                values[i] = CoerceJavaToLua.coerce(parameters[i]);
            return luaGlobals.get(func).call(LuaValue.listOf(values));
        } else
            return luaGlobals.get(func).call();
    }

    private static class CommonLineContainer{
        public CommonLineContainer(ArrayList<CommonLine> commonLines) {
            this.commonLines = commonLines;
        }
        private final ArrayList<CommonLine> commonLines;
    }
}
